/**
 * @File Name: heaptimer.cpp
 * @brief  HeapTimer 类的实现
 * @Author : beyon_sir email:caoboyang.chn@gmail.com
 * @Version : 1.0
 * @Create Date : 2022-11-21
 * 
 */
#include "heaptimer.h"

void HeapTimer::siftup_(size_t i) {
    assert(i >= 0 && i < heap_.size());
    // 获取父节点的索引
    size_t j = (i - 1) / 2;
    while(j >= 0) {
        if(heap_[j] < heap_[i]) { break; }
        // 如果当前定时时长小于父节点 就交换父子关系
        SwapNode_(i, j);
        i = j;
        j = (i - 1) / 2; // 继续判断 新的父节点
    }
}

void HeapTimer::SwapNode_(size_t i, size_t j) {
    assert(i >= 0 && i < heap_.size());
    assert(j >= 0 && j < heap_.size());
    std::swap(heap_[i], heap_[j]);
    ref_[heap_[i].id] = i;
    ref_[heap_[j].id] = j;
} 

/// @brief 当前节点向下调整
/// @param index 
/// @param n 
/// @return 如果成功向下调整 返回1; 如果位置保持不变 返回0
bool HeapTimer::siftdown_(size_t index, size_t n) {
    assert(index >= 0 && index < heap_.size());
    assert(n >= 0 && n <= heap_.size());
    size_t i = index;
    // 判断子节点(默认左子节点)
    size_t j = i * 2 + 1;
    while(j < n) {
        // 先判断左右子节点的大小关系 选择相对较小的超时时间
        if(j + 1 < n && heap_[j + 1] < heap_[j]) ++j;
        if(heap_[i] < heap_[j]) break;
        SwapNode_(i, j);
        i = j;
        j = i * 2 + 1;
    }
    return i > index;
}

/// @brief 添加新定时器(新客户端连接进来)
/// @param id 
/// @param timeout 
/// @param cb 
void HeapTimer::add(int id, int timeout, const TimeoutCallBack& cb) {
    assert(id >= 0);
    size_t i;
    // 判断ref_中有没有对应的文件描述符id 
    if(ref_.count(id) == 0) {
        /* 如果没有 就是新节点：堆尾插入，调整堆 */
        // 查询堆尾
        i = heap_.size();
        // 在ref_中添加文件描述符id与heap_中的索引 的哈希映射
        ref_[id] = i;
        // 堆尾插入
        heap_.push_back({id, Clock::now() + MS(timeout), cb});
        // 将当前的定时器向上调整 跟父亲比较
        siftup_(i);
    } 
    else {
        /* 已有结点：调整堆 */
        i = ref_[id];
        heap_[i].expires = Clock::now() + MS(timeout);
        heap_[i].cb = cb;
        if(!siftdown_(i, heap_.size())) {
            siftup_(i);
        }
    }
}

void HeapTimer::doWork(int id) {
    /* 删除指定id结点，并触发回调函数 */
    if(heap_.empty() || ref_.count(id) == 0) {
        return;
    }
    size_t i = ref_[id];
    TimerNode node = heap_[i];
    node.cb();
    del_(i);
}

void HeapTimer::del_(size_t index) {
    /* 删除指定位置的结点 */
    assert(!heap_.empty() && index >= 0 && index < heap_.size());
    /* 将要删除的结点换到队尾，然后调整堆 */
    size_t i = index;
    size_t n = heap_.size() - 1;
    assert(i <= n);
    if(i < n) {
        // 将将要删除的节点和队尾节点交换位置
        SwapNode_(i, n);
        // 将交换过来的原队尾节点重新进行向下向上调整
        if(!siftdown_(i, n)) {
            siftup_(i);
        }
    }
    /* 队尾元素删除 */
    // 将交换过来到队尾的要删除的节点进行删除
    ref_.erase(heap_.back().id);
    heap_.pop_back();
}

// 在规定超时时间范围内接收到新的客户端数据 就重置超时时间
void HeapTimer::adjust(int id, int timeout) {
    /* 调整指定id的结点 */
    assert(!heap_.empty() && ref_.count(id) > 0);
    heap_[ref_[id]].expires = Clock::now() + MS(timeout);
    // 向下调整
    siftdown_(ref_[id], heap_.size());
}

/// @brief 定时检查heap_，如果有超时结点 就启动清除命令
void HeapTimer::tick() {
    if(heap_.empty()) {
        return;
    }
    while(!heap_.empty()) {
        // 判断堆顶节点
        TimerNode node = heap_.front();
        if(std::chrono::duration_cast<MS>(node.expires - Clock::now()).count() > 0) { break; }
        node.cb(); // 超时了：执行WebServer::CloseConn_操作
        pop();
    }
}

// 删除当前HeapTimer对象节点
void HeapTimer::pop() {
    assert(!heap_.empty());
    del_(0); // 删除堆顶节点
}

void HeapTimer::clear() {
    ref_.clear();
    heap_.clear();
}

/// @brief 获取下次需要tick的时间点
/// @return 
int HeapTimer::GetNextTick() {
    tick(); // 检测出当前已超时的连接并关闭
    size_t res = -1;
    if(!heap_.empty()) {
        // 拿到当前小顶堆头结点对应的超时时间 和 当前时间 的差值(剩余时间) 作为下次tick()的启动时间
        res = std::chrono::duration_cast<MS>(heap_.front().expires - Clock::now()).count();
        if(res < 0) { res = 0; }
    }
    return res;
}